@using System.Text
@using BlazorDatasheet.Edit.DefaultComponents
@using BlazorDatasheet.Render
@using System.Reflection.Metadata
@using BlazorDatasheet.Core.Data
@using BlazorDatasheet.Core.Edit
@using BlazorDatasheet.Core.Events.Edit
@using BlazorDatasheet.Core.Interfaces
@using BlazorDatasheet.Formula.Core
@using Microsoft.JSInterop
@inject IJSRuntime JS;

@if (IsEditing)
{
    <div id="editor" style="position:absolute; @GetEditorSizeStyling()">
        <DynamicComponent
            Type="@ActiveEditorType"
            Parameters="@GetDynamicEditorParameters()"
            @ref="ActiveEditorContainer"/>
    </div>
}


@code {

    [Parameter, EditorRequired]
    public Sheet Sheet { get; set; }

    [Parameter, EditorRequired]
    public Dictionary<string, CellTypeDefinition> CustomCellTypes { get; set; }

    [Parameter, EditorRequired]
    public Dictionary<string, CellTypeDefinition> DefaultCellTypes { get; set; }

    private Sheet? _sheet;

    [Parameter, EditorRequired]
    public CellLayoutProvider CellLayoutProvider { get; set; }

    /// The type of the active editor, which is an ICellEditor
    private Type? ActiveEditorType { get; set; } = typeof(TextEditorComponent);

    /// Whether the Editor is currently editing
    internal bool IsEditing { get; private set; }

    internal IReadOnlyCell EditCell { get; private set; }

    // The currently edited value
    internal string? EditValue { get; private set; }

    /// Whether the edit is "soft" which means that it can be accepted with arrow navigation
    internal bool IsSoftEdit { get; private set; }

    /// The current edit entry mode
    private EditEntryMode _editEntryMode { get; set; }

    /// The key that was pressed to enter the edit
    private string _editEntryKey;

    /// The current cell editor component
    private BaseEditor? _activeCellEditor;

    private bool BeginningEdit { get; set; }

    /// The Dynamic component holding the Active Editor component
    internal DynamicComponent? ActiveEditorContainer { get; set; }

    protected override void OnParametersSet()
    {
        if (_sheet != Sheet)
        {
            if (_sheet != null)
            {
                _sheet.Editor.EditBegin -= EditorOnEditBegin;
                _sheet.Editor.EditFinished -= EditorOnEditFinished;
                _sheet.Editor.EditValueChanged -= EditorOnEditValueChanged;
            }
            _sheet = Sheet;
            _sheet.Editor.EditBegin += EditorOnEditBegin;
            _sheet.Editor.EditFinished += EditorOnEditFinished;
            _sheet.Editor.EditValueChanged += EditorOnEditValueChanged;
        }
        base.OnParametersSet();
    }

    private void EditorOnEditValueChanged(object? sender, string? e)
    {
        if (_activeCellEditor != null)
            _activeCellEditor.HandleEditValueChange(e);
    }

    private void EditorOnEditBegin(object? sender, EditBeginEventArgs e)
    {
        EditValue = e.EditValue;
        ActiveEditorType = GetEditorType(e.Type);
        IsEditing = true;
        IsSoftEdit = e.IsSoftEdit;
        _editEntryMode = e.Mode;
        EditCell = e.Cell;
        _editEntryKey = e.Key ?? string.Empty;
        BeginningEdit = true;
        StateHasChanged();
    }

    private Type GetEditorType(string cellType)
    {
        if (CustomCellTypes?.TryGetValue(cellType, out var customCellType) == true)
            return customCellType.EditorType;

        if (DefaultCellTypes.TryGetValue(cellType, out var defaultCellType))
            return defaultCellType.EditorType;

        return typeof(TextEditorComponent);
    }

    private void EditorOnEditFinished(object? sender, EditFinishedEventArgs e)
    {
        clearCurrentEdit();
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (BeginningEdit)
        {
            BeginningEdit = false;
            if (ActiveEditorContainer == null)
                return;
            _activeCellEditor = (BaseEditor)(ActiveEditorContainer.Instance);
            if (_activeCellEditor == null)
                return;

            _activeCellEditor.BeforeEdit(EditCell, Sheet);
            _activeCellEditor.RequestCancelEdit += HandleEditorRequestCancelEdit;
            _activeCellEditor.RequestAcceptEdit += HandleEditorRequestAcceptEdit;
            _activeCellEditor.BeginEdit(_editEntryMode, EditValue, _editEntryKey);

            if (_editEntryMode != EditEntryMode.None)
            {
                if (!EqualityComparer<ElementReference>.Default.Equals(_activeCellEditor.InputRef, default(ElementReference)))
                {
    // Request focus with a delay so that it the field doesn't capture any oninput events
    // from the text field that is just rendered. If not, there's inconsistent behaviour between
    // WASM and Server.
                    await JS.InvokeVoidAsync("setFocusWithTimeout", _activeCellEditor.InputRef, 0);
                }
            }
        }
    }

    public bool HandleKeyDown(string key, bool ctrlKey, bool shiftKey, bool altKey, bool metaKey)
    {
        if (!IsEditing)
            return false;

        if (_activeCellEditor == null)
            return false;

        return _activeCellEditor.HandleKey(key, ctrlKey, shiftKey, altKey, metaKey);
    }

    private void HandleEditorRequestCancelEdit(object? sender, EventArgs args)
    {
        _sheet?.Editor.CancelEdit();
    }

    private void HandleEditorRequestAcceptEdit(object? sender, EventArgs args)
    {
        _sheet?.Editor.AcceptEdit();
    }

    private void clearCurrentEdit()
    {
        if (_activeCellEditor != null)
        {
            _activeCellEditor.RequestCancelEdit -= HandleEditorRequestCancelEdit;
            _activeCellEditor.RequestAcceptEdit -= HandleEditorRequestAcceptEdit;
        }

        this.IsEditing = false;
        StateHasChanged();
    }

    /// <summary>
    /// Calculates the top/left/width/height styles of the editor container
    /// </summary>
    /// <returns></returns>
    private string GetEditorSizeStyling()
    {
        var strBuilder = new StringBuilder();
        var left = CellLayoutProvider.ComputeLeftPosition(EditCell.Col);
        var top = CellLayoutProvider.ComputeTopPosition(EditCell.Row);

        var mergedPosn = Sheet.Cells.GetMerge(EditCell.Row, EditCell.Col);
        int colSpan = 1;
        int rowSpan = 1;

        if (mergedPosn != null)
        {
            colSpan = mergedPosn.Width;
            rowSpan = mergedPosn.Height;
        }

        var w = CellLayoutProvider.ComputeWidth(EditCell.Col, colSpan) - 1;
        var h = CellLayoutProvider.ComputeHeight(EditCell.Row, rowSpan) - 1;

        strBuilder.Append($"left:{left}px;");
        strBuilder.Append($"top:{top}px;");
        strBuilder.Append($"width:{w}px;");
        strBuilder.Append($"height:{h}px;");
        strBuilder.Append("box-shadow: 0px 0px 4px var(--shadow-overlay-color)");
        var style = strBuilder.ToString();
        return style;
    }


    private IDictionary<string, object> GetDynamicEditorParameters()
    {
        return new Dictionary<string, object>()
        {
            { "OnValueChanged", EventCallback.Factory.Create<string>(this, HandleEditValueChanged) }
        };
    }

    private void HandleEditValueChanged(string newValue)
    {
        Sheet.Editor.EditValue = newValue;
    }

}